require 'socket'
require 'thread'
require 'apiprotocol'
require 'json'

class APIServerParser
  # Possiveis estados da conexao
  RECEIVING_REQ = 0
  SENDING_RESPONSE = 1
  RECEIVING_DOCUMENT = 2
  SENDING_RESPONSE_DATA = 3
  PROCESS = 4

  def initialize(unrealdb, server, socket)
    @server = server
    @socket = socket
    @state = RECEIVING_REQ
    @operation = -1
    @document_received_size = 0
    @document_size = 0
    @document_id = nil
    @document = ""
    @resp_status = API_REQ_OK
    @response_data_sent = 0
    @response_data_size = 0
    @response_data = ""
    @db = unrealdb
  end

  def run
    while true
      ok = true
      ok = process if @state == PROCESS
      
      if !ok
        @server.conn_closed(self)
        return
      end

      read_array = []
      write_array = []
      error_array = []

      if @state == RECEIVING_REQ || @state == RECEIVING_DOCUMENT
        read_array << @socket
      elsif @state == SENDING_RESPONSE || @state == SENDING_RESPONSE_DATA
        write_array << @socket
      end

      result = select(read_array, write_array, error_array)

      next if result == nil

      for sock_read in result[0]
        if sock_read == @socket
          if !do_read_state
            @server.conn_closed(self)
            return
          end
        end
      end

      for sock_write in result[1]
        if sock_write == @socket
          if !do_write_state
            @server.conn_closed(self)
            return
          end
        end
      end
    end   
  end

  def process
    case @operation
    when API_GET_DOCUMENT
      doc = @db.get(@document_id)
      if doc == nil
        @resp_status = API_REQ_NOT_FOUND
        @response_data = nil
        @response_data_size = 0
      else
        @resp_status = API_REQ_OK        
        @response_data = doc
        @response_data_size = doc.length
      end
      @state = SENDING_RESPONSE
    #when GET_ALL_DOCUMENTS
      #@db.establish_replication_connection(@node_id, @src_host)
      #zip = @db.get_all_files_local(@node_id)
      #if zip == nil
        #@resp_status = API_REQ_NOT_FOUND
        #@response_data = nil
        #@response_data_size = 0
      #else
        #@resp_status = API_REQ_OK
        #@response_data = zip
        #@response_data_size = zip.length
      #end
      #@state = SENDING_RESPONSE
    when API_UPDATE_DOCUMENT
      if !@db.update(@document, @document_id)
        @resp_status = API_REQ_NOT_FOUND
      else
        @resp_status = API_REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when API_INSERT_DOCUMENT
      if !@db.put(@document, @document_id)
        @resp_status = API_REQ_NOT_FOUND
      else
        @resp_status = API_REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when API_REMOVE_DOCUMENT
      if !@db.remove(@document_id)
        @resp_status = API_REQ_NOT_FOUND
      else
        @resp_status = API_REQ_OK
      end
      @state = SENDING_RESPONSE
      @response_data = nil
      @response_data_size = 0
    when API_QUERY
      doc = query_analiser(@document_id)
      if doc == nil
        @resp_status = API_REQ_NOT_FOUND
        @response_data = nil
        @response_data_size = 0
      else
        @resp_status = API_REQ_OK        
        @response_data = doc
        @response_data_size = doc.length
      end
      @state = SENDING_RESPONSE
    else
      return false
    end
    return true
  end

  def do_read_state
    case @state
    when RECEIVING_REQ
      receive_req
    when RECEIVING_DOCUMENT
      receive_document
    else
      return false
    end
    return true
  end

  def do_write_state
    case @state
    when SENDING_RESPONSE
      send_response
    when SENDING_RESPONSE_DATA
      send_response_data
    else
      return false
    end
    return true
  end

  def receive_document
    expected_size = @document_size - @document_received_size

    data = @socket.read(expected_size)

    return false if data == nil

    if data.length != expected_size
      @document += data
      @document_received_size -= data.length
      return true
    end

    @document_received_size = 0
    @document += data
    @state = PROCESS

    return true
  end

  def receive_req
    begin
      data = @socket.readline
    rescue
      return false
    end

    return false if data == nil

    array = data.split(";")

    @operation = Integer(array[0])
    @document_id = array[1]
    #@node_id = array[2]
    @document_size = Integer(array[2])
    #@src_host = array[4].strip

    case @operation
    when API_GET_DOCUMENT
      @state = PROCESS
    when API_QUERY
      @state = PROCESS
    when API_CLOSE_CONNECTION
      @state = PROCESS
    when API_UPDATE_DOCUMENT
      @state = RECEIVING_DOCUMENT
    when API_INSERT_DOCUMENT
      @state = RECEIVING_DOCUMENT
    when API_REMOVE_DOCUMENT
      @state = PROCESS
    else
      return false
    end

    return true
  end

  def send_response
    packed = @resp_status.to_s + ";" + @response_data_size.to_s
    
    @socket.puts(packed)

    if @response_data_size != 0
      @state = SENDING_RESPONSE_DATA
    else
      flush_vars
    end

    return true
  end

  def send_response_data    
    remaining_size = @response_data_size - @response_data_sent

    data = @socket.send(@response_data[@response_data_sent..-1], 0)

    return false if data == nil

    if data != remaining_size
      @response_data_sent += data
      return true
    end

    flush_vars
    return true
  end

  def close
    @socket.close
  end

private
  def query_analiser(query)
    tokens = query.split("=>")
    tokens.each{|t| t.strip}
    return nil if tokens == []
    return query_process(tokens)
  end
  
  def query_process(tokens)
    #FAZ BUSCA PELO PRIMERI TOKEN, DEPOIS VERIFICA SE O PROXIMO EH UM CAMPO OU UM DOC_ID
    # SE FOR UM CAMPO VAI NESSE CAMPO E VERIFICA SE EH UM CAMPO OU UM DOC_ID ETC..
    # SE FOR UM DOC_ID FAZ OUTRA BUSCA
    # O ULTIMO TOKEN SE FOR UM CAMPO, RETORNA UM DOC SO COM O CAMPO
    doc_id = tokens.first
    j_doc = @db.get(doc_id)
    return nil if j_doc == nil
    doc = JSON.parse(j_doc)
    
    last_token = doc_id
    
    tokens = tokens[1..-1] if tokens.length > 1
    tokens.each do |token|
      if doc.has_key?(token)
        if doc[token].is_a?(Hash) and doc[token].has_key?('document_id')
          #EH UM DOCUMENTO
          j_doc = @db.get(doc[token]['document_id'])
          doc = JSON.parse(j_doc)
        else
          doc = doc[token]
        end
        last_token = token
      else
        return nil
      end
    end
    doc = (doc.is_a?(Hash) ? doc : {last_token => doc})
    return JSON.generate(doc)
  end
  
  def flush_vars
    @state = RECEIVING_REQ
    @operation = -1
    @document_received_size = 0
    @document_size = 0
    @document_id = nil
    @document = ""
    @resp_status = API_REQ_OK
    @response_data_sent = 0
    @response_data_size = 0
    @response_data = ""
    #@node_id = nil
  end
end
